﻿using System;
using System.Diagnostics;
using System.Linq.Expressions;

namespace ExpressionMapper
{
    using MappingExpressionGenerator = Func<Expression, Type, Expression, ParameterExpression, Expression>;

    /// <summary>
    /// Container class for mapper generators.
    /// </summary>
    public static partial class Generators
    {
        /// <summary>
        /// Returns generator for assignable types.
        /// </summary>
        public static MappingExpressionGenerator Assignable()
        {
            return (fromExpr, to, customMapping, instanceDictionary) =>
                       {
                           if (to.IsAssignableFrom(fromExpr.Type) &&
                               (IsAssignable(fromExpr.Type) ||
                                IsAssignableNullable(fromExpr.Type, to)))
                           {
                               return (customMapping ?? GenerateAssignableExpression(fromExpr, to));
                           }
                           if(to == typeof(Delegate) && IsDelegate(fromExpr.Type))
                               return (customMapping ?? GenerateAssignableExpression(fromExpr, to));

                           return null;
                       };
        }

        private static Expression GenerateAssignableExpression(Expression fromExpr, Type to)
        {
            Debug.WriteLine(string.Format("\tAssignable ({0}): {1} to {2}", fromExpr.NodeType, fromExpr.Type.Name,
                                          to.Name));
            if (fromExpr.Type != to)
                return Expression.Convert(fromExpr, to);

            return fromExpr;
        }

        public static bool IsAssignableNullable(Type fromType, Type toType)
        {
            return (IsNullable(fromType) && IsNullable(toType));
            //    ||  (IsNullable(fromType) && fromType.UnderlyingSystemType == toType) 
            //    || (IsNullable(toType) && toType.UnderlyingSystemType == fromType) ;
        }

        public static bool IsDelegate(Type t)
        {
            return typeof (Delegate).IsAssignableFrom(t);
        }

        public static bool IsAssignable(Type t)
        {
            return IsPrimitive(t) || t.IsEnum || typeof(Delegate).IsAssignableFrom(t) || t.IsValueType;
        }

        public static bool IsPrimitive(Type t)
        {
            return t.IsPrimitive || t == typeof(DateTime) || t == typeof(string) || t == typeof(Decimal);
        }
    }
}